<?php

class fop_Object_Message {

  protected $_id;
  protected $_foplineName;
  protected $_tableName;
  protected $_message = array();  
  protected $_creatorEmail;
  protected $_creationDate;
  protected $_description;
  protected $_status;
  protected $_versionNumber;
      
  /* definition of the message table */
  static protected $_dbMessageTableName = 'messages';
  static protected $_dbMessageIdCol = 'id';
  static protected $_dbMessagefoplineNameCol = 'foplineName';
  static protected $_dbMessageTableNameCol = 'tableName';
  static protected $_dbMessageMessageCol = 'message';
  static protected $_dbMessageDescriptionCol = 'description';
  static protected $_dbMessageCreatorEmailCol = 'creatorEmail';
  static protected $_dbMessageCreationDateCol = 'creationDate';  
  static protected $_dbMessageStatusCol = 'status';  
  static protected $_dbMessageVersionNumberCol = 'versionNumber'; 
  
    
  static protected $_validStatusList = array('','current','supported','deprecated');

  static protected $_allowedRenderMode = array('FOPTable','RegularExpression'); 
  
  public function __construct($foplineName,$tableName,$id = '',$status = '',$versionNumber = '' ,$data = array(),$special = '') {
  	
  	if(empty($data)) {
   	  //We validate the name
  	  self::_validateName($foplineName); 	  
  	  self::_validateName($tableName);
  	  //We get the data from the database
      $db = Zend_Registry::get('dt_dbs')->getDb('fop','message','key');
      $select = $db->select()->from(self::$_dbMessageTableName);
      $select->where($db->quoteInto(self::$_dbMessagefoplineNameCol ." = ?", $foplineName));
      $select->where($db->quoteInto(self::$_dbMessageTableNameCol ." = ?", $tableName));
	  
	  //We look wish message is required in the following order: id, version,status. For status, we return the most current one
	  //If Id is provided, we go for this one
	  if(!empty($id)) $select->where($db->quoteInto(self::$_dbMessageIdCol ." = ?", $id));
	  elseif(!empty($versionNumber)) $select->where($db->quoteInto(self::$_dbMessageVersionNumberCol ." = ?", $versionNumber));
	  elseif(!empty($status) && in_array  ( $status , self::$_validStatusList )) {
	  	$select->where($db->quoteInto(self::$_dbMessageStatusCol ." = ?", $status));
	  	$select->order(self::$_dbMessageCreationDateCol.' DESC');
	  } elseif(!empty($special) && 'latest' == $special) {
	  	$select->order(self::$_dbMessageIdCol.' DESC');
	  	$select->limit(1,0);	   	
	  } elseif(!empty($special) && 'whatever' == $special) {
	  	//nothing, we don t really care what is the message returned (used for history purpose)	   	
	  }
	  else throw new fop_Object_Exception('Message not found',8); 

      $res = $db->fetchAll($select->__toString());
    
      if(empty($res)) {
  	    require_once('fop/objects/Exception.php');
  	    throw new fop_Object_Exception('Message not found',8); 
      }
    $data = $res[0];
  	}
    $this->_id = $data[self::$_dbMessageIdCol];
    $this->_foplineName = $data[self::$_dbMessagefoplineNameCol];
    $this->_tableName = $data[self::$_dbMessageTableNameCol];
    $this->_description = $data[self::$_dbMessageDescriptionCol];
    $this->_creatorEmail = $data[self::$_dbMessageCreatorEmailCol];
    $this->_creationDate = $data[self::$_dbMessageCreationDateCol];
    $this->_message = self::decodeMessage($data[self::$_dbMessageMessageCol]);
    $this->_status = $data[self::$_dbMessageStatusCol];
    $this->_versionNumber = $data[self::$_dbMessageVersionNumberCol];	

      	
  }	//end constructor

  public function asArray() {
  	$results = array();
    $results['id'] = $this->_id;
    $results['foplineName'] = $this->_foplineName;
    $results['tableName'] = $this->_tableName;
    $results['description'] = $this->_description;
    $results['creatorEmail'] = $this->_creatorEmail;
    $results['creationDate'] = $this->_creationDate;
    $results['message'] = $this->_message;
    $results['status'] = $this->_status;
    $results['versionNumber'] = $this->_versionNumber;	  	
    return $results;
  }


  public function getHistory() {
      $db = Zend_Registry::get('dt_dbs')->getDb('fop','message','key');
      $select = $db->select()->from(self::$_dbMessageTableName);
      $select->where($db->quoteInto(self::$_dbMessagefoplineNameCol ." = ?", $this->_foplineName));
      $select->where($db->quoteInto(self::$_dbMessageTableNameCol ." = ?", $this->_tableName));
      $select->order(self::$_dbMessageCreationDateCol.' DESC');
      //$select->limit(1,0);
      $res = $db->fetchAll($select->__toString());
      
      if(empty($res)) return array();
      $historyList = array();
      
      foreach($res as $num => $data) {
      	$tempMessage = new 	fop_Object_Message('','','','','' ,$data);
      	$tempArray = $tempMessage->asArray();
      	
      	//we unset the message to not overburden the data returned
      	unset($tempArray['message']);
      	$historyList[] = $tempArray;
      	
      }
      return $historyList;
        	
  }

  public function getId() { return $this->_id;}
  public function getfoplineName() { return $this->_foplineName;}
  public function getTableName() { return $this->_tableName;}
  public function getMessage() { return $this->_message;}  
  public function getCreatorEmail() { return $this->_creatorEmail;}
  public function getCreationDate() { return $this->_creationDate;}
  public function getDescription() { return $this->_description;}
  public function getStatus() { return $this->_status;}
  public function getVersionNumber() { return $this->_versionNumber;}
  
  /*
  This function returns an array of function objects prefilled with the data used by the FOP
  */
  public function getFunctionsObjects() {
  	require_once('fop/objects/Function.php');
  	$message = $this->getMessage();
  	$functionsData = $message['functions'];
  	if(empty($functionsData)) return array();
  	
  	$list = array();
  	
  	foreach($functionsData as $num => $data) {
  		

  		
  		$function = new fop_Object_Function($data['id']);
 		if(!empty($data['structureData'])) $function->loadFoplineData($data['structureData']);
  		$list[] = $function;
  	}
  	return $list;	
  }
  
  public function getStructuresObjects() {
  	require_once('fop/objects/Structure.php');
  	$message = $this->getMessage();
  	$structuresData = $message['structure'];
  	if(empty($structuresData)) return array();
  	
  	$list = array();
  	
  	foreach($structuresData as $num => $data) {
  		$structure = new fop_Object_Structure('',$data);
  		$list[] = $structure;
  	}
  	return $list;	
  }  
  
  
  //the message is stored as a base64 gzdefalted serialized PHP
  static public function decodeMessage($data) {
	
    $decoded = @unserialize(gzinflate(base64_decode($data)));
    if(empty($decoded)) {
      require_once('fop/objects/Exception.php');
      throw new fop_Object_Exception('An issue occured while decoding message',16);
    }  
    return  $decoded;	
  }

  static public function encodeMessage($data) {
  	if(!is_array($data)) throw new fop_Object_Exception('input parameter is not an array',12); 
    $encoded = @base64_encode(gzdeflate(serialize($data),9));
    if(empty($encoded)) throw new fop_Object_Exception('An issue occured while encoding message',17); 
    return $encoded;   	
  }
	
  static public function listing($foplineName,$tableName) {
    $db = Zend_Registry::get('dt_dbs')->getDb('fop','message','key');
    $select = $db->select()->from(self::$_dbMessageTableName);
    $select->where($db->quoteInto(self::$_dbMessagefoplineNameCol ." = ?", $foplineName));
    $select->where($db->quoteInto(self::$_dbMessageTableNameCol ." = ?", $tableName));
    $res = $db->fetchAll($select->__toString());  

    if(empty($res)) return array();

    //We must reinterpret the rows so it is independant to the way it is stored
    $results = array();
    foreach($res as $num => $row) {
    	$fopline = new fop_Object_Message('','','','','',$row);
    	$results[] = $fopline->asArray();	
    }
    
    return $results;
      
  } //end list
  

  public function render($mode) {
	if(!in_array($mode,self::$_allowedRenderMode)) {
  	  require_once('fop/objects/Exception.php');
  	  throw new fop_Object_Exception('Mode is not an authorized render mode',3);		
	}

	$functions = $this->getFunctionsObjects();
	$structures = $this->getStructuresObjects(); 
		
	if('FOPTable' == $mode) {
		//We render the structure
		$structureRender = array();
		//We render the functions
		//Subtility, the fop may be before or after the structure

		$functionsRender = array();
		$functionsRender['before'] = array();
		$functionsRender['after'] = array();	
		if(!empty($functions)) {
			foreach($functions as $num => $function) {
				$data = $function->render($mode);
				foreach($data as $where => $string) $functionsRender[$where][] = $string;
			}
		}
		
		$structureRender = array();
		if(!empty($structures)) {
			foreach($structures as $num => $structure) $structureRender[] = $structure->render($mode);
		}
		
		
		//We prepend the Fop Name
		$pieces= array(strToUpper($this->_foplineName));
		if(!empty($functionsRender['before'])) $pieces[] = implode(',',$functionsRender['before']);
		if(!empty($structureRender)) $pieces[] = implode(',',$structureRender);
		if(!empty($functionsRender['after'])) $pieces[] = implode(',',$functionsRender['after']);
		//et voila	
			
		return implode(',',$pieces);	
	
	}//end mode == foptable
	
	if('RegularExpression' == $mode) {
		//We render the structure
		
		$structureRender = array();
		if(!empty($structures)) {
			foreach($structures as $num => $structure) $structureRender[] = $structure->render($mode);
		}
		
		
		//We prepend the Fop Name
		$pieces= array(strToUpper($this->_foplineName));
		if(!empty($structureRender)) $pieces[] = implode('',$structureRender);
		//et voila	
			
		return '/^'.implode('',$pieces).'$/';	
	
	}//end mode == foptable	
	

  }

  
  //only to be used when no message at all exists for the fopline
  static public function createBase($params) {

  	
  	//We clean the data provided
  	//We validate the existence of the different parent objects
    require_once('fop/objects/fopline.php');
    $fopline = new fop_Object_Fopline($params['foplineName'],$params['tableName']);
  	$foplineName = $fopline->getName();

  	  	
    require_once('fop/objects/table.php');
    $table = new fop_Object_Table($params['tableName']);
  	$tableName = $table->getName();
  	
    
    //We check if any Message is not already existing
    $existing = false;
    $result = fop_Object_Message::listing($foplineName,$tableName);
    if(!empty($result)) $existing = true;
    
    if($existing) {
  	    require_once('fop/objects/Exception.php');
  	    throw new fop_Object_Exception('Message is already existing',10);     	
    }


    //OK we can create the Message
	$email = Zend_Registry::get('app_user')->getEmail();
  	$creationDate = time();
  	
  	//default message definition
  	$defaultMessage = array('structure' => array(),'functions' => array());
  	
  	$data = array(
      self::$_dbMessagefoplineNameCol => $foplineName,
      self::$_dbMessageTableNameCol => $tableName,
      self::$_dbMessageDescriptionCol => 'automatically generated at fopline creation',
      self::$_dbMessageCreatorEmailCol => $email,
      self::$_dbMessageCreationDateCol => $creationDate,
      self::$_dbMessageMessageCol => self::encodeMessage($defaultMessage),
      self::$_dbMessageStatusCol => 'current',
      self::$_dbMessageVersionNumberCol => 0
  	);
    $db = Zend_Registry::get('dt_dbs')->getDb('fop','message','key');
    $db->insert(self::$_dbMessageTableName, $data); 	
  	return true;
  	
  } //end create

  public function update($messageDefinition,$messageFormat,$changeDescription) {
    if('serializedPHP' == $messageFormat) {
      $message = @unserialize($messageDefinition);
      if(empty($message)) {
      	require_once('fop/objects/Exception.php');
  	    throw new fop_Object_Exception('Issue decoding message',18); 
      } //Issue decoding message
    } elseif('JSON' == $messageFormat) {
      $message = @json_decode($messageDefinition);
      if(empty($message)) {
      	require_once('fop/objects/Exception.php');
  	    throw new fop_Object_Exception('Issue decoding message',18); 
      } //Issue decoding message    	
    }

  	//We clean the description, only constraint is that it should be valid HTML
	require_once('htmlpurifier/library/HTMLPurifier.auto.php');
  	$purifier = new HTMLPurifier();
    $description = $purifier->purify($changeDescription);

    //We validate the message definition
    
    $result = $this->_validateMessage($message);
    if(!is_bool($result)) {
      	require_once('fop/objects/Exception.php');
  	    throw new fop_Object_Exception('Message error: '.$result,19);    	
    }
    
    //OK we can create the Message
	$email = Zend_Registry::get('app_user')->getEmail();
  	$creationDate = time();
  	
  	
  	$data = array(
      self::$_dbMessagefoplineNameCol => $this->_foplineName,
      self::$_dbMessageTableNameCol => $this->_tableName,
      self::$_dbMessageDescriptionCol => $description,
      self::$_dbMessageCreatorEmailCol => $email,
      self::$_dbMessageCreationDateCol => $creationDate,
      self::$_dbMessageMessageCol => self::encodeMessage($message)
  	);
    $db = Zend_Registry::get('dt_dbs')->getDb('fop','message','key');
    $db->insert(self::$_dbMessageTableName, $data); 	
  	return true;  	
  }


  /*  make the message the current version
  *@param void
  *@return void
  *@throw fop_Object_Exception if an issue occurs
  */
  public function makeCurrent() {
    
    Zend_Registry::get('dt_log')->log('Changing current version for the message', Zend_Log::DEBUG);
  	
  
  	$previousCurrentMessage = new fop_Object_Message($this->getfoplineName(),$this->getTableName(),'','current');
  	Zend_Registry::get('dt_log')->log('Previous current version found, version:'.$previousCurrentMessage->getVersionNumber().' id:'.$previousCurrentMessage->getId(), Zend_Log::DEBUG);
  	
  	//We check that the id of the current message is higher than the last current version (can t promote an older message)
  	if($previousCurrentMessage->getId() >= $this->getId()) {
      	require_once('fop/objects/Exception.php');
  	    throw new fop_Object_Exception('Previous message with status current has a bigger id than candidate message',22);   			
  	}	
  	
  	//We update the version of the message
  	$previousVersion = $previousCurrentMessage->getVersionNumber();
  	if(!is_numeric($previousVersion)) {
      	require_once('fop/objects/Exception.php');
  	    throw new fop_Object_Exception('Version is not a number',21);   		
  	}
  	$newVersion = $previousVersion + 1;
  	
    $db = Zend_Registry::get('dt_dbs')->getDb('fop','message','key');
 
    $data = array(
          self::$_dbMessageStatusCol      => 'current',
          self::$_dbMessageVersionNumberCol     => $newVersion
    );
    $n = $db->update(self::$_dbMessageTableName, $data, self::$_dbMessageIdCol.' = '.$this->getId());
  	if(1 != $n) {
      	require_once('fop/objects/Exception.php');
  	    throw new fop_Object_Exception('Issue occured while updating record',22);   		
  	}    
    
  	//We move the version of the previous message to deprecated
    $data = array(
          self::$_dbMessageStatusCol      => 'deprecated'
    );
    $n = $db->update(self::$_dbMessageTableName, $data, self::$_dbMessageIdCol.' = '.$previousCurrentMessage->getId());
  	if(1 != $n) {
      	require_once('fop/objects/Exception.php');
  	    throw new fop_Object_Exception('Issue occured while updating record',22);   		
  	} 
  	
  	  	
  	//We update the current object
  	$this->_status = 'current';
  	$this->_versionNumber = $newVersion;
  	
  	//we return 
  	return;
  		
  }



  /* validate that the message has a valid format
   *@return boolean true if message is correctly defined, error message else
  */ 
  protected function _validateMessage($message) {
    return true;	
  }
  
  
  static protected function	_validateName($name) {
    
    if(empty($name)) {
  	  require_once('fop/objects/Exception.php');
  	  throw new fop_Object_Exception('Mandatory data is missing',1);
    }
    
    //Message and table name can only be lowercase alphabet characters and less or equal 32 characters
  	if(preg_match('/[^a-zA-Z0-9]/',$name)) {
  	  require_once('fop/objects/Exception.php');
  	  throw new fop_Object_Exception('Fopline or table name can only be alphabet lower case characters',2);  		
  	}
  	if(32 < strlen($name)) {
  	  require_once('fop/objects/Exception.php');
  	  throw new fop_Object_Exception('Fopline or table name must be less than 33 characters',3);
  	}
  	return true;	
  }
}